#include "Pch.h"
#include "SequenceWindow.h"
#include "Config.h"

BEGIN_EVENT_TABLE(SequenceWindow, ClickManager<wxPanel>)
	EVT_SIZE(SequenceWindow::OnSize)
	EVT_PAINT(SequenceWindow::OnPaint)
	EVT_SCROLLWIN(SequenceWindow::OnScroll)
	EVT_TIMER(wxID_ANY, SequenceWindow::OnTimer)
END_EVENT_TABLE()

SequenceWindow::SequenceWindow(wxWindow *parent, MainWindow *mainWindow, PDBManager* pdbManager) :
ClickManager<wxPanel>(parent, wxTAB_TRAVERSAL | wxBORDER_SUNKEN | wxCLIP_SIBLINGS),
font(10, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL),
boldFont(10, wxFONTFAMILY_TELETYPE, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_BOLD, false),
timer(this)
{
	this->mainWindow = mainWindow;
	dragging = 0;
	for (int i=0; i<2; i++) {
		scroll[i].position = scroll[i].range = scroll[i].width = 0;
	}
	maxNameWidth = 0;
	lastActiveChain = -1;
	this->pdbManager = pdbManager;
	wxClientDC dc(this);
	wxFont oldFont = dc.GetFont();
	dc.SetFont(font);
	fontHeight = dc.GetCharHeight();
	fontWidth = dc.GetCharWidth()+2;
	dc.SetFont(oldFont);

	assert(fontHeight > 0 && fontWidth > 0);

	SetScrollBars(1, 0, 0);

	SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));

	/*
	wxSizer *sizer = new wxBoxSizer(wxVERTICAL);

	sizer->Add(combo, 0, wxEXPAND);
	sizer->AddSpacer(4);
	sizer->Add(list, 1, wxEXPAND);

	SetSizer(sizer);

	//*/
}

SequenceWindow::~SequenceWindow() {}

void SequenceWindow::OnScroll(wxScrollWinEvent& evt) {
	int i = 0;
	if (evt.GetOrientation() == wxVERTICAL) {
		i = 1;
	}
	int delta = 0;
	int type = evt.GetEventType();

	if (type == wxEVT_SCROLLWIN_TOP) {
		delta = -scroll[i].position;
	}
	else if (type == wxEVT_SCROLLWIN_BOTTOM) {
		// Overshooting will be fixed by SetScrollBars.
		delta = scroll[i].range;
	}
	else if (type == wxEVT_SCROLLWIN_LINEUP) {
		delta = -1;
	}
	else if (type == wxEVT_SCROLLWIN_LINEDOWN) {
		delta = 1;
	}
	else if (type == wxEVT_SCROLLWIN_PAGEUP) {
		delta = -scroll[i].width;
	}
	else if (type == wxEVT_SCROLLWIN_PAGEDOWN) {
		delta = scroll[i].width;
	}
	else if (type == wxEVT_SCROLLWIN_THUMBTRACK || type == wxEVT_SCROLLWIN_THUMBRELEASE) {
		delta = evt.GetPosition() - scroll[i].position;
	}

	if (i == 0) {
		SetScrollBars(0, delta, 0);
	}
	else {
		SetScrollBars(0, 0, delta);
	}
}

void SequenceWindow::SetScrollBars(int fullUpdate, int deltax, int deltay) {
	int newPositions[2] = {scroll[0].position + deltax, scroll[1].position + deltay};
	const int orient[2] = {wxHORIZONTAL, wxVERTICAL};

	scroll[0].range = 0;
	maxNameWidth = 0;
	for (int i=0; i<pdbManager->numChains; i++) {
		if (scroll[0].range < pdbManager->chains[i]->numChainResidues) {
			scroll[0].range = pdbManager->chains[i]->numChainResidues;
		}
		int len = strlen(pdbManager->chains[i]->idString);
		if (len > maxNameWidth) {
			maxNameWidth = len;
		}
	}
	if (pdbManager->align) scroll[0].range = pdbManager->align->length;

	maxNameWidth = maxNameWidth*(fontWidth-2) + 8 + 1;
	scroll[1].range = pdbManager->numChains;

	wxRect rect = GetClientRect();

	scroll[0].width = (rect.width-maxNameWidth) / fontWidth;
	scroll[1].width = rect.height / fontHeight;

	int needRefresh = 0;
	for (int i=0; i<2; i++) {
		if (scroll[i].width <= 0) scroll[i].width = 1;
		if (scroll[i].range <= 0) scroll[i].range = 1;

		if (scroll[i].range < newPositions[i] + scroll[i].width) {
			newPositions[i] = scroll[i].range - scroll[i].width;
		}
		if (newPositions[i] < 0) newPositions[i] = 0;
		if (newPositions[i] != scroll[i].position) {
			scroll[i].position = newPositions[i];
			needRefresh = 1;
		}
		if (fullUpdate)
			SetScrollbar(orient[i], scroll[i].position, scroll[i].width, scroll[i].range, true);
		else
			SetScrollPos(orient[i], scroll[i].position, true);
	}
	if (needRefresh)
		Refresh(0);
}

void SequenceWindow::UpdatePDB(PDBManager *manager) {
	pdbManager = manager;
	SetScrollBars(1, -scroll[0].position, -scroll[1].position);
	Refresh(0);
}

void SequenceWindow::OnSize(wxSizeEvent &evt) {
	GetClientSize(&width, &height);
	SetScrollBars(1, 0, 0);
	wxPanel::OnSize(evt);
}


void SequenceWindow::UpdateDisplay() {
	Refresh(0);
}

void SequenceWindow::ChangeSelection(int chain, int index) {
	int dx = 0;
	if (pdbManager->align) {
		int i;
		for (i=0; i<pdbManager->align->length; i++) {
			if (pdbManager->align->indices[chain][i] == index) {
				index = i;
				break;
			}
		}
		// Not a part of the chain, so not displayed.
		if (i == pdbManager->align->length)
			return;
	}
	else {
		if (index >= pdbManager->chains[chain]->numChainResidues)
			return;
	}
	if (scroll[0].position > index) {
		dx = index - scroll[0].position;
	}
	else if (scroll[0].position + scroll[0].width <= index) {
		dx = index + 1 - scroll[0].width - scroll[0].position;
	}

	int dy = 0;
	if (scroll[1].position > chain) {
		dy = chain - scroll[1].position;
	}
	else if (scroll[1].position + scroll[1].width <= chain) {
		dy = chain + 1 - scroll[1].width - scroll[1].position;
	}
	if (dx || dy) SetScrollBars(0, dx, dy);
	Refresh(0);
}

void SequenceWindow::OnPaint(wxPaintEvent& evt) {
	wxPaintDC dc(this);

    wxBitmap memBMP(width, height);
    wxMemoryDC memDC(&dc);
	memDC.SelectObject(memBMP);

	wxColour oldColours[2] = {
		memDC.GetTextForeground(),
		memDC.GetTextBackground(),
	};
	const wxBrush oldBrush = memDC.GetBrush();
	const wxPen oldPen = memDC.GetPen();
	wxFont oldFont = memDC.GetFont();
	int oldMode = memDC.GetBackgroundMode();

	memDC.SetPen(*wxTRANSPARENT_PEN);
	memDC.SetFont(boldFont);
	memDC.SetBackgroundMode(wxTRANSPARENT);
	//memDC.SetBackgroundMode(wxSOLID);

	int pos = 0;

	int xStart = maxNameWidth;
	//memDC.SetBrush(wxBrush(colors[0]));
	//memDC.DrawRectangle(maxNameWidth-3, 0, 3, rect.height);

	Color curBrushColor;
	Color curTextColor;

	memDC.SetBrush(WX_BRUSH(curBrushColor = config->colorTextBgChain));
	memDC.SetTextForeground(WX_COLOR(curTextColor = config->colorText));
	int y = 0;

	memDC.DrawRectangle(0, y, maxNameWidth, height);
	for (int c = scroll[1].position; y < height; c++, y+=fontHeight) {
		if (c == pdbManager->numChains) break;
		ManagedChain *chain = pdbManager->chains[c];
		memDC.DrawText(wxString(chain->idString, wxConvUTF8), 4, y);
	}
	memDC.SetFont(font);

	Color bgColors[2] = {
		config->colorTextBg,
		config->colorTextBg2
	};
	int x = xStart;
	for (int res = scroll[0].position; x < width; ) {
		int width = 10 - res%10;
		CHANGE_BRUSH_COLOR(memDC, bgColors[(res / 10) & 1], curBrushColor);
		memDC.DrawRectangle(x, 0, width*fontWidth, height);
		x += fontWidth * width;
		res += width;
	}

	y = 0;
	wxString text(_T("0"));
	for (int c = scroll[1].position; c < pdbManager->numChains && y < height; c++, y+=fontHeight) {
		ManagedChain *chain = pdbManager->chains[c];
		int x = xStart;
		int pos;
		for (pos = scroll[0].position; x < width; pos++, x+=fontWidth) {
			int index;
			if (!pdbManager->align) {
				if (pos >= chain->numChainResidues) break;
				index = pos;
			}
			else {
				if (pos >= pdbManager->align->length) break;
				index = pdbManager->align->indices[c][pos];
			}

			unsigned int flags = 0;
			if (index >= 0) flags = chain->residueInfo[index].flags;

			if (flags & (RESIDUE_SELECTED | RESIDUE_LAST_SELECTED)) {
				if (flags & (RESIDUE_LAST_SELECTED)) {
					CHANGE_BRUSH_COLOR(memDC, config->colorTextSelRecentBg, curBrushColor);
					CHANGE_TEXT_COLOR(memDC, config->colorTextSelRecent, curTextColor);
				}
				else {
					CHANGE_BRUSH_COLOR(memDC, config->colorTextSelBg, curBrushColor);
					CHANGE_TEXT_COLOR(memDC, config->colorTextSel, curTextColor);
				}
				memDC.DrawRectangle(x, y, fontWidth, fontHeight);
			}
			else if (!(flags & RESIDUE_RENDER) || chain->residues[index].firstAtom == -1) {
				CHANGE_TEXT_COLOR(memDC, config->colorHidden, curTextColor);
			}
			else {
				Color resColor = pdbManager->GetResidueColor(c, index, 1);
				Color textColor = config->colorText;
				int dtext = abs(resColor.r - textColor.r) + abs(resColor.g - textColor.g) + abs(resColor.b - textColor.b);
				int dbg = abs(resColor.r - config->colorTextBg.r) + abs(resColor.g - config->colorTextBg.g) + abs(resColor.b - config->colorTextBg.b);

				Color bgColor;
				if ((pos / 10) & 1)
					bgColor = config->colorTextBg2;
				else
					bgColor = config->colorTextBg;

				if (dtext < dbg) {
					textColor = resColor;
					// Not needed, since I'm drawing transparent text.  May change that later.
					// memDC.SetBrush(WX_BRUSH(bgColors[(res / 10) & 1]));
				}
				else {
					if (resColor.r != config->colorTextBg.r || resColor.g != config->colorTextBg.g || resColor.b != config->colorTextBg.b) {
						CHANGE_BRUSH_COLOR(memDC, resColor, curBrushColor);
						memDC.DrawRectangle(x, y, fontWidth, fontHeight);
					}
					else {
						// Not needed, since I'm drawing transparent text.  May change that later.
						// memDC.SetBrush(WX_BRUSH(bgColors[(res / 10) & 1]));
					}
				}
				CHANGE_TEXT_COLOR(memDC, textColor, curTextColor);
			}

			//memDC.DrawRectangle(x, y, fontWidth, fontHeight);
			//memDC.SetBrush(wxBrush(colors[(res / 5) & 1]));
			//memDC.SetTextBackground(colors[(res / 10) & 1]);
			if (index >= 0) text[0] = ShortNames[chain->seq->seq[index]];
			else text[0] = '-';
			// Add an extra pixel to either side (fontWidth reflects this already).
			// Needed for ClearType fonts under windows, also makes a little more
			// readable, anyways.
			memDC.DrawText(text, x+1, y);
		}
	}

	if (dragging) {
		memDC.SetLogicalFunction(wxINVERT);
		int x1, x2, y1, y2;
		GetDragRect(&x1, &y1, &x2, &y2);
#ifdef WIN32
		// For whatever reason, under Windows, an x length skip between dashes
		// results in drawing a pixel and then skipping x-1 pixels.
		// So this actually results in a dotted line.  This is not
		// a wxWidgets issue, but a Windows one, though a workaround
		// would be easy to add to wxWidgets.
		wxDash dashes[] = {0,2};
		wxPen pen(WX_COLOR(config->colorText), 1, wxUSER_DASH);
		pen.SetDashes(2, dashes);
#else
		wxPen pen(WX_COLOR(config->colorText), 1, wxDOT);
#endif
		memDC.SetPen(pen);

		if (x1 > x2) {
			int temp = x1;
			x1 = x2;
			x2 = temp;
		}
		if (y1 > y2) {
			int temp = y1;
			y1 = y2;
			y2 = temp;
		}

		int drawLeft = 1;
		if (x1 < maxNameWidth) {
			x1 = maxNameWidth;
			if (scroll[0].position)
				drawLeft = 0;
		}
		if (x2 < maxNameWidth)
			x2 = maxNameWidth;
		if (x2 >= width && scroll[0].position + scroll[0].width >= scroll[0].range) {
			x2 = width-1;
		}

		if (y1 < 0 && scroll[1].position == 0) {
			y1 = 0;
		}
		if (y2 >= height && scroll[1].position + scroll[1].width >= scroll[1].range) {
			y2 = height-1;
		}

		if (drawLeft)
			memDC.DrawLine(wxPoint(x1, y1), wxPoint(x1, y2));
		memDC.DrawLine(wxPoint(x2, y1), wxPoint(x1, y1));
		memDC.DrawLine(wxPoint(x2, y2), wxPoint(x2, y1));
		memDC.DrawLine(wxPoint(x1, y2), wxPoint(x2, y2));

		memDC.SetLogicalFunction(wxCOPY);
	}

	memDC.SetBackgroundMode(oldMode);
	memDC.SetFont(oldFont);
	memDC.SetPen(oldPen);
	memDC.SetTextForeground(oldColours[0]);
	memDC.SetTextBackground(oldColours[1]);
	memDC.SetBrush(oldBrush);
	dc.Blit(0, 0, width, height, &memDC, 0, 0);
	// Just to make sure cleanup goes nicely.
	memDC.SelectObject(wxNullBitmap);
}

void SequenceWindow::MultipleClick(int clickCount) {
	if (clickCount < 2) return;
	if (lastChain < 0) {
		ResetClickCount();
		return;
	}
	ManagedChain *chain = pdbManager->chains[lastChain];
	int numResidues = chain->numChainResidues;
	int secondary = chain->residueInfo[lastResidue].flags & RESIDUE_SECONDARY;
	if (clickCount == 2) {
		for (int delta = -1; delta < 2; delta+=2) {
			// Start at last residue to make sure it's selected.
			for (int res = lastResidue; res>=0 && res < numResidues; res += delta) {
				if ((chain->residueInfo[res].flags & RESIDUE_SECONDARY) != secondary)
					break;
				chain->residueInfo[res].flags |= RESIDUE_SELECTED;
			}
		}
	}
	else if (clickCount == 3 && (secondary & RESIDUE_IS_BETA)) {
		// Use RESIDUE_LAST_SELECTED as temporary flag.  Second should already be set.
		chain->residueInfo[lastResidue].flags |= RESIDUE_LAST_SELECTED | RESIDUE_SELECTED;
		// Only set it back to 1 when I find a new beta strand, as all the old ones will be fully selected.
		int changed = 1;
		while (changed) {
			changed = 0;
			for (int res=0; res<numResidues-1; res++) {
				if (chain->residueInfo[res].flags & RESIDUE_LAST_SELECTED && (chain->residueInfo[res+1].flags & RESIDUE_IS_BETA))
					chain->residueInfo[res+1].flags |= RESIDUE_LAST_SELECTED | RESIDUE_SELECTED;
			}
			for (int res=numResidues-1; res > 0; res--) {
				if (chain->residueInfo[res].flags & RESIDUE_LAST_SELECTED && (chain->residueInfo[res-1].flags & RESIDUE_IS_BETA))
					chain->residueInfo[res-1].flags |= RESIDUE_LAST_SELECTED | RESIDUE_SELECTED;
			}

			for (int i=0; i<chain->numBetaPairs; i++) {
				int s1 = chain->betaPairs[i].start1;
				int s2 = chain->betaPairs[i].start2;
				// Lazy check to see if one's flagged and the other isn't.
				if ((chain->residueInfo[s1].flags ^ chain->residueInfo[s2].flags) & RESIDUE_LAST_SELECTED) {
					chain->residueInfo[s1].flags |= RESIDUE_LAST_SELECTED | RESIDUE_SELECTED;
					chain->residueInfo[s2].flags |= RESIDUE_LAST_SELECTED | RESIDUE_SELECTED;
					changed = 1;
				}
			}
		}
	}
	else {
		pdbManager->SelectChain(lastChain);
		ResetClickCount();
	}
	//pdbManager->statusBar->SetResidueText(-1, -1);

	// Clear last selected residue, if set.  Also redraw and update flags.
	pdbManager->UpdateSelectedResidues(0, RESIDUE_LAST_SELECTED, REDRAW_ALL);
}

void SequenceWindow::UpdateDragSel(int startx, int starty, int lastx, int lasty) {
	wxMouseState mouseState = wxGetMouseState();
	if (mouseState.LeftDown() && mouseState.ShiftDown()) {
		// Make rendering a little more responsive while dragging.
		// Experimentally, difference isn't terribly noticeable.
		lastx = mouseState.GetX();
		lasty = mouseState.GetY();
		ScreenToClient(&lastx, &lasty);
	}
	pdbManager->UpdateResidues(0, 0, RESIDUE_LAST_SELECTED, 0);
	int c1, c2, r1, r2;
	GetClickedPosition(&c1, &r1, startx, starty);
	GetClickedPosition(&c2, &r2, lastx, lasty);

	if (c1 > c2) {
		int temp = c1;
		c1 = c2;
		c2 = temp;
	}
	if (r1 > r2) {
		int temp = r1;
		r1 = r2;
		r2 = temp;
	}

	if (r1 < 0) r1 = 0;

	for (int c = c1; c <= c2; c++) {
		if (c < 0 || c >= pdbManager->numChains) continue;
		for (int r = r1; r <= r2; r++) {
			int res;
			if (!pdbManager->align) {
				if (r >= pdbManager->chains[c]->numChainResidues) break;
				res = r;
			}
			else {
				if (r >= pdbManager->align->length) break;
				res = pdbManager->align->indices[c][r];
				if (res < 0) continue;
			}
			pdbManager->chains[c]->residueInfo[res].flags |= RESIDUE_LAST_SELECTED;
		}
	}

	pdbManager->Redraw(REDRAW_ALL | REDRAW_NOW);
}

void SequenceWindow::DragDone(int cancelled) {
	if (dragging) {
		dragging = 0;
		if (cancelled) {
			pdbManager->UpdateSelectedResidues(0, RESIDUE_LAST_SELECTED, REDRAW_ALL | REDRAW_NOW);
		}
		else {
			pdbManager->UpdateResidues(RESIDUE_LAST_SELECTED, RESIDUE_SELECTED, RESIDUE_LAST_SELECTED, REDRAW_ALL | REDRAW_NOW);
		}
	}
}

void SequenceWindow::MouseDown(unsigned int button, int clickCount, unsigned int flags, int x, int y) {
	if (flags & MOUSE_RIGHT) {
		if (dragging) DragDone(1);
		ResetStartPos();
	}
	if ((flags & MOUSE_BUTTONS) == MOUSE_LEFT) {
		if (clickCount == 1) {
			pdbManager->SaveUndoState();
		}
		if (flags & MOUSE_DRAGGING) {
			/*if ((flags & MOUSE_SHIFT)) {
				dragging = 1;
				UpdateDragSel(x, y, x, y);
				return;
			}//*/
		}
		else if (clickCount > 1) {
			MultipleClick(clickCount);
		}
	}
}

void SequenceWindow::OnTimer(wxTimerEvent &evt) {
	if (dragging) {
		int xStart, yStart, xStop, yStop;
		GetDragRect(&xStart, &yStart, &xStop, &yStop);
		MouseDrag(MOUSE_LEFT, xStart, yStart, xStop, yStop);
	}
}

void SequenceWindow::MouseDrag(unsigned int flags, int xStart, int yStart, int xStop, int yStop) {
	flags &= ~MOUSE_MIDDLE;
	MouseMove(flags, xStop, yStop);
	//pdbManager->statusBar->SetResidueText(-1, -1);
	unsigned int buttons = (flags & MOUSE_BUTTONS) & ~MOUSE_MIDDLE;

	if (buttons == MOUSE_LEFT) {
		if (!dragging) {
			// Only start dragging when left button just pressed.
			// So using right to cancel selection works as expected.
			if (!(flags & MOUSE_DRAGGING_STARTED)) return;
			ResetStartPos();
			xStart = xStop;
			yStart = yStop;
		}
		dragging = 1;

		int dx = 0;
		int dy = 0;
		if (xStop < maxNameWidth && scroll[0].position) {
			dx = -((maxNameWidth - xStop + 16)/16);
			if (dx < -scroll[0].position)
				dx = -scroll[0].position;
		}
		else if (xStop > width && scroll[0].position + scroll[0].width < scroll[0].range) {
			dx = ((xStop - width + 16)/16);
			if (dx + scroll[0].position + scroll[0].width > scroll[0].range) {
				dx = scroll[0].range - scroll[0].position - scroll[0].width;
			}
		}

		if (yStop < 0 && scroll[1].position) {
			dy = -((-yStop + 16)/16);
			if (dy < -scroll[1].position)
				dy = -scroll[1].position;
		}
		else if (yStop > height && scroll[1].position + scroll[1].width < scroll[1].range) {
			dy = ((yStop - height + 16)/16);
			if (dy + scroll[1].position + scroll[1].width > scroll[1].range) {
				dy = scroll[1].range - scroll[1].position - scroll[1].width;
			}
		}

		if (dx || dy) {
			SetStartPos(xStart - dx * fontWidth, yStart - dy * fontHeight);
			timer.Start(50, 1);
			SetScrollBars(0, dx, dy);
		}

		UpdateDragSel(xStart - dx * fontWidth, yStart - dy * fontHeight, xStop, yStop);
	}
}

void SequenceWindow::MouseMove(unsigned int flags, int x, int y) {
	int chain, residue;
	if (!(flags & MOUSE_LEAVING) && GetClickedResidue(&chain, &residue, x, y)) {
		mainWindow->statusBar->SetResidue(chain, residue);
	}
	else {
		mainWindow->statusBar->SetResidue(-1, -1);
	}
}

void SequenceWindow::MouseUp(unsigned int button, int clickCount, unsigned int flags, int x, int y) {
	if (button & MOUSE_FXN_KEYS)
		return;

	if (dragging) {
		if (button == MOUSE_LEFT) {
			DragDone(0);
			ResetStartPos();
		}
		return;
	}

	// If dragged or more than one down, click count is 0.
	if (clickCount >= 1) {
		if (button == MOUSE_LEFT) {
			// Multiple left clicks handled on mouse down.
			if (clickCount == 1) {
				int startx, starty, lastx, lasty;
				GetDragRect(&startx, &starty, &lastx, &lasty);
				if (GetClickedResidue(&lastChain, &lastResidue, startx, starty)) {
					ManagedChain *chain = pdbManager->chains[lastChain];
					chain->residueInfo[lastResidue].flags ^= RESIDUE_SELECTED;

					pdbManager->UpdateResidues(0, 0, RESIDUE_LAST_SELECTED, 0);

					if (chain->residueInfo[lastResidue].flags & RESIDUE_SELECTED) {
						chain->residueInfo[lastResidue].flags |= RESIDUE_LAST_SELECTED;
						//pdbManager->UpdateChecks();
						//pdbManager->statusBar->SetResidueText(lastChain, lastResidue);
						pdbManager->ChangeSelection(lastChain, lastResidue);
					}
					else {
						//pdbManager->statusBar->SetResidueText(-1, -1);
					}
					pdbManager->Redraw(REDRAW_ALL);
				}
				else {
					if (x < maxNameWidth && lastChain >= 0 && lastChain < pdbManager->numChains) {
						pdbManager->ToggleChainSelection(lastChain);
						pdbManager->Redraw(REDRAW_ALL);
					}
					else {
						pdbManager->UpdateSelectedResidues(0, RESIDUE_SELECTED | RESIDUE_LAST_SELECTED, REDRAW_ALL);
					}
					lastChain = lastResidue = -1;
					//pdbManager->statusBar->SetResidueText(-1, -1);
					ResetClickCount();
				}
			}
		}
		else if (button == MOUSE_RIGHT) {
			mainWindow->ContextMenu();
			ResetClickCount();
		}
	}
}

void SequenceWindow::GetClickedPosition(int *chain, int *pos, int x, int y) {
	// Multiply is needed to make rounding right when x, y are negative.
	// Result before division (And after) will always be >= 0.
	*chain = (scroll[1].position * fontHeight + y) / fontHeight;
	*pos = (scroll[0].position * fontWidth + x - maxNameWidth) / fontWidth;
}

int SequenceWindow::GetClickedResidue(int *chain, int *residue, int x, int y) {
	int pos;
	GetClickedPosition(chain, &pos, x, y);
	if (!pdbManager->align) {
		*residue = pos;
	}
	else if (pos >= 0 && pos < pdbManager->align->length && *chain >= 0 && *chain < pdbManager->numChains) {
		*residue = pdbManager->align->indices[*chain][pos];
	}
	else {
		*residue = -1;
	}

	if (*chain < 0 || *chain >= pdbManager->numChains) return 0;

	if (*residue < 0 || x < maxNameWidth || *residue >= pdbManager->chains[*chain]->numChainResidues || x > width || y > height || y < 0) return 0;
	return 1;
}

void SequenceWindow::MouseWheel(unsigned int wheel, int delta, unsigned int flags, int lastActionWasWheel) {
	int dx = 0, dy = 0;
	if (wheel == 0 && scroll[1].width < scroll[1].range) {
		dy = -delta;
	}
	else if (wheel == 0) {
		// Mimics Windows' behavior.
		dx = -5*delta;
	}
	else {
		dx = 5*delta;
	}
	SetScrollBars(0, dx, dy);
}
